//    Travel US - For users of travel.usairways.com
//    intended for US Airways employees and their dependents
//    This app will help you track your flights and display
//    Availability, flight status and other non-rev info
//    Copyright (C) 2014  Renats Stozkovs
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.bc004346.travelus.DataAccess;

import com.bc004346.travelus.Model.Domain.Flight;
import com.bc004346.travelus.Model.Domain.FlightSearchParams;
import com.bc004346.travelus.Model.Domain.Passenger;
import com.bc004346.travelus.Model.Domain.Reservation;
import com.bc004346.travelus.Model.Domain.StringList;
import com.bc004346.travelus.Model.TravelUsException;
import com.bc004346.travelus.Model.Tuple;

import org.apache.commons.lang3.StringEscapeUtils;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import java.io.IOException;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import static com.bc004346.travelus.Utility.Utility.DATE;
import static com.bc004346.travelus.Utility.Utility.DAY;
import static com.bc004346.travelus.Utility.Utility.LONG_DATE;
import static com.bc004346.travelus.Utility.Utility.SECOND;
import static com.bc004346.travelus.Utility.Utility.SHORT_DATE;
import static com.bc004346.travelus.Utility.Utility.SHORT_TIME;
import static com.bc004346.travelus.Utility.Utility.log;

/**
 * Created by Renats on 1/20/14.
 * This is a data access screen scraper against
 * Travel US website
 */
public class TravelUsScreenScraper {

    //<editor-fold desc="Members">

    private static final String PARENTHESIS_START = "(";
    private static final String PARENTHESIS_END = ")";
    private static final String JSESSIONID = "JSESSIONID";
    private static final String ICE_SESSIONS = "ice.sessions";
    private static final String END = "end...";
    private static final String TR = "tr";
    private static final String NSC = "NSC_wt_Qspe_QDJ_FUD_443";
    //<editor-fold desc="Book Flight section">
    private static final String BOOK_FLIGHT_NUMBER_0 = "td[id$=:j_id106:0:j_id107-0-0]";
    private static final String BOOK_FLIGHT_NUMBER_1 = "td[id$=:j_id106:1:j_id107-0-0]";
    private static final String BOOK_FLIGHT_NUMBER_2 = "td[id$=:j_id106:2:j_id107-0-0]";
    private static final String BOOK_DEPARTURE_0 = "td[id$=:j_id116:0:j_id117-0-0]";
    private static final String BOOK_DEPARTURE_1 = "td[id$=:j_id116:1:j_id117-0-0]";
    private static final String BOOK_DEPARTURE_2 = "td[id$=:j_id116:2:j_id117-0-0]";
    private static final String BOOK_ARRIVAL_0 = "td[id$=:j_id123:0:j_id124-0-0]";
    private static final String BOOK_ARRIVAL_1 = "td[id$=:j_id123:1:j_id124-0-0]";
    private static final String BOOK_ARRIVAL_2 = "td[id$=:j_id123:2:j_id124-0-0]";
    private static final String BOOK_ARRIVAL_ALT_0 = "td[id$=:j_id123:0:j_id127-0-0]";
    private static final String BOOK_ARRIVAL_ALT_1 = "td[id$=:j_id123:1:j_id127-0-0]";
    private static final String BOOK_ARRIVAL_ALT_2 = "td[id$=:j_id123:2:j_id127-0-0]";
    private static final String BOOK_FIRST_CLASS_0 = "td[id$=:j_id169:0:j_id174-0-0]";
    private static final String BOOK_FIRST_CLASS_1 = "td[id$=:j_id169:1:j_id174-0-0]";
    private static final String BOOK_FIRST_CLASS_2 = "td[id$=:j_id169:2:j_id174-0-0]";
    private static final String BOOK_COACH_0 = "td[id$=0:j_id199-0-0]";
    private static final String BOOK_COACH_1 = "td[id$=1:j_id199-0-0]";
    private static final String BOOK_COACH_2 = "td[id$=2:j_id199-0-0]";
    private static final String BOOK_FLIGHT_SEARCH_ERROR = "[address$=Error";
    //</editor-fold>
    //<editor-fold desc="Check PBT section">
    private static final String PBT_NON_REVS = "span[id$=:j_id78]";
    private static final String PBT_DESTINATION = "span[id$=:j_id54]";
    private static final String PBT_FLIGHT_NUMBER = "span[id$=:j_id46]";
    private static final String PBT_DEPARTURE_POINT = "span[id$=j_id50]";
    private static final String PBT_DEPARTURE_DATE = "span[id$=:j_id58]";
    private static final String PBT_FIRST_CAPACITY = "span[id$=:j_id62]";
    private static final String PBT_COACH_CAPACITY = "span[id$=:j_id82]";
    private static final String PBT_COACH_NON_REVS = "span[id$=:j_id98]";
    private static final String PBT_FIRST_SEATS_AVAILABLE = "a[id$=:j_id74]";
    private static final String PBT_COACH_SEATS_AVAILABLE = "a[id$=:j_id94]";
    //</editor-fold>
    //<editor-fold desc="Profile section">
    private static final String PROFILE_NAME = "profileForm:j_id142";
    private static final String PROFILE_BADGE = "profileForm:j_id145";
    private static final String PROFILE_PNR = "a[id$=:listPnr]";
    private static final String PROFILE_TRAVEL_DATE = "span[id$=:j_id195]";
    private static final String PROFILE_BOOKING_TYPE = "span[id$=:j_id198]";
    private static final String PROFILE_SENIORITY = "profileForm:j_id148";
    private static final String PROFILE_IS_PNR_ACTIVE = "td[style=width:45px;overflow:hidden;;text-align:center;]";
    private static final String PROFILE_DETAILS_PASSENGERS = "pnrDetailsForm:j_id117";
    private static final String PROFILE_DETAILS_ITINERARY = "pnrDetailsForm:itins";
    private static final String PROFILE_TABLE = "table[class=iceDatTblScrlBdyTbl]";
    //</editor-fold>
    //<editor-fold desc="Checkin section">
//    private static final String CHECKIN_SUCCESS = "checkinConfirmation.jsf";
//    private static final String CHECKIN_TABLE = "table[class=iceDatTblScrlBdyTbl]";
    //</editor-fold>
    //<editor-fold desc="Check PBT section">
    private static final String CHECK_PBT_DATA_TABLE_ROW = "tr[class^=icePnlGrdRow]";
    //    private static final String CHECK_PBT_FORM_NON_REVS = "checkPbtForm:j_id69:0:j_id108";
    private static final String CHECK_PBT_FORM_AVAILABILITY1 = "checkPbtForm:j_id69:0:j_id83";
    //    private static final String CHECK_PBT_FORM_AVAILABILITY2 = "checkPbtForm:j_id69:0:j_id75";
//    private static final String CHECK_PBT_BOARDING_PRIORITY = "Boarding Priority";
    private static final String CHECK_PBT_FIRST = "First";
    private static final String CHECK_PBT_TOTAL_CAPACITY = "Total Capacity";
    private static final String CHECK_PBT_AUTHORIZED = "Authorized";
    private static final String CHECK_PBT_BOOKED = "Booked";
    private static final String CHECK_PBT_SPACE_AVAILABLE = "Space Available";
    private static final String CHECK_PBT_NON_REVS_LISTED = "Non Revs Listed";
    //</editor-fold>
    //<editor-fold desc="URI's section">
    private static final String URI_PROFILE = "https://travel.usairways.com/us/search/profile.jsf";
    private static final String URI_CHECK_PBT = "https://travel.usairways.com/us/search/checkPbt.jsf";
    //    private static final String URI_CHECK_IN = "https://travel.usairways.com/us/checkin/checkin.jsf";
    private static final String URI_ITINERARY = "https://travel.usairways.com/us/search/itinerary.jsf";
    private static final String URI_PBT_TRACKER = "https://travel.usairways.com/us/search/pbtTracker.jsf";
    private static final String URI_PNR_DETAILS = "https://travel.usairways.com/us/search/pnrDetails.jsf";
    private static final String URI_SECURITY_CHECK = "https://travel.usairways.com/us/search/j_security_check";
    private static final String URI_SELECT_PASSENGERS = "https://travel.usairways.com/us/search/passengers.jsf";
    private static final String URI_BOOKING_CONFIRMATION = "https://travel.usairways.com/us/search/confirmation.jsf";
    private static final String URI_SEND_RECEIVE_UPDATES = "https://travel.usairways.com/us/block/send-receive-updates";
    //    private static final String URI_CHECKIN_CONFIRMATION = "https://travel.usairways.com/us/checkin/checkin.jsf?boardPoint=%s&recLoc=%s&badgeNo=%s&companyCode=US";
    //</editor-fold>
    private static final String DATA_TABLE_ROW = "tr[class^=iceDatTblRow]";
    private static final String SPAN = "span";
    private static final String IFRAME = "iframe";
    private static final String UPDATE = "UPDATE";
    private static final String CONTENT = "content";
    private static final String STARTING = "starting...";
    private static final String USER_AGENT = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.3; WOW64; Trident/7.0; LCJB)";
    private static final String LOGIN_PAGE_ERROR_MESSAGE = "Please enter your user name and password that you use for Wings";
    private final int mTimeout = 30000;
    private String sessionId = "";
    private String profileSession = "";
    private String nsc = "";
    private String name = "";
    private String badge = "0";
    private Date seniority = new Date();
    private String mUsername = "";
    private String mPassword = "";
    private Boolean isLoggedIn = false;
    private int counter = 0;

    //</editor-fold>
    //<editor-fold desc="Constructor">

    public TravelUsScreenScraper(String name, String badge, Date seniority) {
        setName(name);
        setBadge(badge);
        setSeniority(seniority);
    }

    //</editor-fold>
    //<editor-fold desc="Properties">

    private static void trustAllHosts() {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

            }

            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[]{};
            }
        }};

        // Install the all-trusting trust manager
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection
                    .setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Date getSeniority() {
        return seniority;
    }

    void setSeniority(Date seniority) {
        this.seniority = seniority;
    }

    public String getBadge() {
        return badge;
    }

    void setBadge(String badge) {
        this.badge = badge;
    }

    public String getName() {
        return name;
    }

    void setName(String name) {
        this.name = name;
    }

    public Boolean getIsLoggedIn() {
        return isLoggedIn;
    }

    private String getRandom() {
        return String.valueOf(Math.random());
    }

    private int getOffset() {
        return TimeZone.getDefault().getRawOffset();
    }

    //</editor-fold>
    //<editor-fold desc="Public Methods">

    public Boolean doLogin(String username, String password)
            throws IOException, TravelUsException {

        log(STARTING);

        Connection.Response res;
        Boolean result;
        Document doc;
        String csrf;

        mUsername = username;
        mPassword = password;

        trustAllHosts();
        try {
            res = Jsoup.connect(URI_ITINERARY)
                    .userAgent(USER_AGENT)
                    .timeout(mTimeout)
                    .ignoreHttpErrors(true)
                    .followRedirects(true)
                    .execute();
        } catch (UnknownHostException e) {
            throw new TravelUsException("Travel US is unresponsive, please try again later");
        }

        doc = res.parse();
        csrf = doc.select("input[name=csrf]").val();
        sessionId = res.cookie(JSESSIONID);
        nsc = res.cookie(NSC);

        res = Jsoup.connect(URI_SECURITY_CHECK)
                .timeout(mTimeout)
                .userAgent(USER_AGENT)
                .header("Accept", "text/html, application/xhtml+xml, */*")
                .header("Accept-Language", "en-US,en;q=0.7,ru;q=0.3")
                .header("Content-Type", "application/x-www-form-urlencoded")
                .header("Accept-Encoding", "gzip, deflate")
                .header("Host", "travel.usairways.com")
                .header("DNT", "1")
                .header("Connection", "Keep-Alive")
                .header("Cache-Control", "no-cache")
                .data("j_username", username)
                .data("j_password", password)
                .data("csrf", csrf)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie("ice.sessions", "")
                .referrer(URI_ITINERARY)
                .method(Connection.Method.POST).execute();
        doc = res.parse();
        sessionId = res.cookie(JSESSIONID);

        result = !doc.text().contains("You have entered an incorrect user name or password");

        isLoggedIn = result;
        log(String.format("end... returning %s", result));
        if (!result) log(doc.text());

        return result;
    }

    public List<Reservation> getProfile(int daysToSearch)
            throws IOException, TravelUsException, ParseException {

        log(STARTING);

        List<Reservation> reservations = new ArrayList<Reservation>();
        String bookingType;
        String isActive;
        Element table;
        Document doc;
        String pnr;
        Date date;

        doc = getProfilePage();

        ParseUserData(doc);

        table = doc.select(PROFILE_TABLE).get(1);
        profileSession = doc.select(IFRAME).first().id().substring(14, 36);
        for (Element row : table.select(DATA_TABLE_ROW)) {
            try {
                pnr = row.select(PROFILE_PNR).text();
                date = DATE.parse(row.select(PROFILE_TRAVEL_DATE).text());
                bookingType = row.select(PROFILE_BOOKING_TYPE).text();
                isActive = row.select(PROFILE_IS_PNR_ACTIVE).html();


                if (Math.abs((date.getTime() - new Date().getTime()) / DAY) <= daysToSearch
                        && isActive.length() == 0)
                    reservations.add(new Reservation(pnr, date, bookingType));
            } catch (Exception e) {
                log(e.getMessage());
            }
        }

        log(String.format("end... returning %d reservations(s)", reservations.size()));

        return reservations;
    }

    public Tuple<List<Flight>, List<Passenger>> getReservationDetails(String pnr)
            throws IOException, ParseException {

        log(STARTING);

        List<Flight> flights;
        List<Passenger> pax;
        Connection con;
        Element table;
        Document doc;

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_PROFILE)
                .cookie(JSESSIONID, sessionId)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .cookie(NSC, nsc)
                .userAgent(USER_AGENT)
                .data("ice.event.target", "profileForm:listings:0:listPnr")
                .data("ice.event.captured", "profileForm:listings:0:listPnr")
                .data("profileForm", "profileForm")
                .data("javax.faces.RenderKitId", "ICEfacesRenderKit")
                .data("profileForm:phoneNum", "")
                .data("profileForm:emailAddress", "")
                .data("profileForm:eligibleFirstNameHidden", "")
                .data("profileForm:eligibleLastNameHidden", "")
                .data("profileForm:eligibleIdHidden", "")
                .data("profileForm:eligiblePassword", "")
                .data("profileForm:eligiblePasswordConfirm", "")
                .data("profileForm:to", "")
                .data("profileForm:loginActionHidden", "")
                .data("profileForm:j_id135Expanded", "")
                .data("profileForm:recLookup", "")
                .data("profileForm:j_id183Expanded", "")
                .data("profileForm:j_id208Expanded", "")
                .data("profileForm:_idcl", "profileForm:listings:0:listPnr")
                .data("recloc", pnr).data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "profileForm:listings:0:listPnr")
                .data("rand", getRandom()).timeout(mTimeout)
                .method(Connection.Method.POST);
        addStandardInput(con);
        con.execute();

        doc = Jsoup.connect(URI_PNR_DETAILS).referrer(URI_PROFILE)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .userAgent(USER_AGENT)
                .timeout(mTimeout).get();

        table = doc.getElementById(PROFILE_DETAILS_ITINERARY);
        flights = getFlights(table, pnr);

        table = doc.getElementById(PROFILE_DETAILS_PASSENGERS);
        pax = getPassengers(table);

        Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .userAgent(USER_AGENT)
                .timeout(mTimeout).get();

        log(String.format("end... returning %d flight(s) and %d passenger(s)", flights.size(), pax.size()));

        return new Tuple<List<Flight>, List<Passenger>>(flights, pax);
    }

    public Flight checkAvailability(Flight flight)
            throws IOException, TravelUsException {

        log(String.format("starting... (Flight = %s)", flight));

        String session;
        Document doc;

        doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, "")
                .timeout(mTimeout).get();
        session = doc.select(IFRAME).first().id().substring(14, 36);

        if (doc.text().contains(LOGIN_PAGE_ERROR_MESSAGE)) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {

                doc = Jsoup.connect(URI_CHECK_PBT).referrer(URI_ITINERARY)
                        .cookie(JSESSIONID, sessionId)
                        .cookie(NSC, nsc)
                        .cookie(ICE_SESSIONS, "")
                        .timeout(mTimeout).get();
                session = doc.select(IFRAME).first().id().substring(14, 36);
            }
        }

        Connection con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES).referrer(URI_CHECK_PBT)
                .cookie(JSESSIONID, sessionId)
                .cookie(ICE_SESSIONS, session + "#1")
                .cookie(NSC, nsc)
                .userAgent(USER_AGENT)
                .data("ice.event.target", "checkPbtForm:search")
                .data("ice.event.captured", "checkPbtForm:search")
                .data("checkPbtForm:search", "Get PBT Detail")
                .data("checkPbtForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("checkPbtForm:flightNumber", String.valueOf(flight.getFlightNumber()))
                .data("checkPbtForm:board", flight.getOrigin())
                .data("checkPbtForm:board_idx", "")
                .data("checkPbtForm:dest", flight.getDestination())
                .data("checkPbtForm:dest_idx", "")
                .data("checkPbtForm:deptdate", SHORT_DATE.format(flight.getDeparture()))
                .data("checkPbtForm:_idcl", "checkPbtForm:j_id69:0:j_id103")
                .data("checkPbtForm:_idcheckPbtForm:deptdatesp", "")
                .data("ice.session", session)
                .data("ice.view", "1")
                .data("ice.focus", "checkPbtForm:j_id69:0:j_id103")
                .data("rand", getRandom())
                .timeout(mTimeout);
        addStandardInput(con);

        doc = con.post();

        flight = parseAvailability(flight, doc);

        log(END);

        return flight;
    }

    public ArrayList<Flight> searchFlights(FlightSearchParams params)
            throws IOException, TravelUsException {

        log(STARTING);

        ArrayList<Flight> flights = new ArrayList<Flight>();
        Date startFunction = new Date();

        Document doc;
        Connection con;

        Document login = Jsoup.connect(URI_ITINERARY)
                .referrer(URI_PROFILE)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .userAgent(USER_AGENT)
                .timeout(mTimeout).get();

        profileSession = login.select(IFRAME).first().id().substring(14, 36);

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .cookie(NSC, nsc)
                .data("ice.event.target", "itineraryForm:search")
                .data("ice.event.captured", "itineraryForm:search")
                .data("itineraryForm:search", "Search")
                .data("itineraryForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("itineraryForm:bookingType", "SA")
                .data("itineraryForm:tripType", params.isOneWay() ? "OneWay" : "RoundTrip")
                .data("itineraryForm:board", params.getOrigin())
                .data("itineraryForm:board_idx", "")
                .data("itineraryForm:dest", params.getDestination())
                .data("itineraryForm:dest_idx", "")
                .data("itineraryForm:dd", DATE.format(params.getDeparture()))
                .data("itineraryForm:j_id50", "ANY");
        addStandardInput(con);

        if (!params.isOneWay())
            con.data("itineraryForm:rd", DATE.format(params.getReturn()))
                    .data("itineraryForm:rtod", "ANY");
        else
            con.data("itineraryForm:_idcl", "");

        doc = con
                .data("itineraryForm:_idcl", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "itineraryForm:search")
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        if (doc.select(UPDATE).first().select(BOOK_FLIGHT_SEARCH_ERROR).size() == 1 ||
                doc.select("attribute").text().contains("errorLabel"))
            throw new TravelUsException(doc.select(UPDATE).first().select(CONTENT).text());

        doc = extractInnerHTML(doc, 0);

        extractFlights(params.getDeparture(), flights, doc);

        log(String.format("Function returns %d flight(s) in %s seconds",
                flights.size(),
                String.valueOf((new Date().getTime() - startFunction.getTime()) / SECOND)));
        return flights;
    }


    public ArrayList<Flight> searchReturnFlights(FlightSearchParams params)
            throws IOException, TravelUsException {

        log(STARTING);

        ArrayList<Flight> flights = new ArrayList<Flight>();
        Date startFunction = new Date();
        Document doc;
        Connection con;

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .data("ice.submit.partial", "true")
                .data("ice.event.target", "itineraryForm:searchResult:1:j_id420")
                .data("ice.event.captured", "itineraryForm:searchResult:1.1")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false")
                .data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined")
                .data("ice.event.x", "163")
                .data("ice.event.y", "275")
                .data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("itineraryForm", "")
                .data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1")
                .data("javax.faces.RenderKitId", "")
                .data("itineraryForm:bookingType", "SA")
                .data("itineraryForm:tripType", "RoundTrip")
                .data("itineraryForm:board", params.getOrigin())
                .data("itineraryForm:board_idx", "")
                .data("itineraryForm:dest", params.getDestination())
                .data("itineraryForm:dest_idx", "")
                .data("itineraryForm:dd", DATE.format(params.getDeparture()))
                .data("itineraryForm:j_id50", "ANY");

        if (!params.isOneWay())
            con.data("itineraryForm:rd", DATE.format(params.getReturn()))
                    .data("itineraryForm:rtod", "ANY");

        doc = con
                .data("itineraryForm:j_id78FirstPane", "height:100%;width:77%;")
                .data("itineraryForm:j_id78SecondPane", "height:100%;width:20%;")
                .data("itineraryForm:j_id78InPercent", "")
                .data("itineraryForm:searchResult:0:_idcl", "")
                .data("itineraryForm:_idcl", "itineraryForm:searchResult:1")
                .data("itineraryForm:searchResult:1.1", "itineraryForm:searchResult:1.1")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "itineraryForm:search")
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        if (doc.select(UPDATE).first().select(BOOK_FLIGHT_SEARCH_ERROR).size() == 1 ||
                doc.select("attribute").text().contains("errorLabel"))
            throw new TravelUsException(doc.select(UPDATE).first().select(CONTENT).text());

        doc = extractInnerHTML(doc, 3);

        extractFlights(params.getDeparture(), flights, doc);

        log(String.format("Function returns %d flight(s) in %s seconds",
                flights.size(),
                String.valueOf((new Date().getTime() - startFunction.getTime()) / SECOND)));
        return flights;
    }

    public List<Flight> pbtTracker()
            throws IOException, TravelUsException {

        log(STARTING);

        List<Flight> flights = new ArrayList<Flight>();
        Integer coachSpaceAvailable;
        Integer firstSpaceAvailable;
        String departurePoint;
        Integer firstCapacity;
        Integer coachCapacity;
        Integer flightNumber;
        Integer firstNonRev;
        Integer coachNonRev;
        Date departureTime;
        String destination;
        Date departureDate;
        String origin;
        Integer start;
        Document doc;
        Integer end;

        doc = Jsoup.connect(URI_PBT_TRACKER).referrer(URI_ITINERARY)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, "")
                .timeout(mTimeout).get();

        if (doc.text().contains(LOGIN_PAGE_ERROR_MESSAGE)) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {

                doc = Jsoup.connect(URI_PBT_TRACKER).referrer(URI_ITINERARY)
                        .cookie(JSESSIONID, sessionId)
                        .cookie(NSC, nsc)
                        .cookie(ICE_SESSIONS, "")
                        .timeout(mTimeout).get();
            }
        }

        for (Element row : doc.select(DATA_TABLE_ROW)) {
            // let's just catch all exceptions
            // because input is not sanitized and
            // may contain flights in the past with only a few fields filled out
            try {
                flightNumber = Integer.valueOf(row.select(PBT_FLIGHT_NUMBER).text().substring(3));
                departurePoint = row.select(PBT_DEPARTURE_POINT).text();
                origin = departurePoint.substring(0, 3);
                start = departurePoint.indexOf(PARENTHESIS_START);
                end = departurePoint.lastIndexOf(PARENTHESIS_END);
                departureTime = SHORT_TIME.parse(departurePoint.substring(start + 1, end));
                destination = row.select(PBT_DESTINATION).text();
                departureDate = DATE.parse(row.select(PBT_DEPARTURE_DATE).text());
                firstCapacity = Integer.valueOf(row.select(PBT_FIRST_CAPACITY).text());
                firstSpaceAvailable = Integer.valueOf(row.select(PBT_FIRST_SEATS_AVAILABLE).text());
                firstNonRev = Integer.valueOf(row.select(PBT_NON_REVS).text());
                coachCapacity = Integer.valueOf(row.select(PBT_COACH_CAPACITY).text());
                coachSpaceAvailable = Integer.valueOf(row.select(PBT_COACH_SEATS_AVAILABLE).text());
                coachNonRev = Integer.valueOf(row.select(PBT_COACH_NON_REVS).text());
                departureDate = new Date(departureDate.getTime() + departureTime.getTime() + getOffset());
                flights.add(new Flight(flightNumber, origin, destination, departureDate, firstCapacity,
                        firstSpaceAvailable, firstNonRev, coachCapacity, coachSpaceAvailable, coachNonRev));
            } catch (Exception e) {
                log(e.getMessage());
            }
        }

        return flights;
    }

    public void selectFlight(Flight flight)
            throws IOException, TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .cookie(NSC, nsc)
                .data("ice.event.target", flight.getFlightIndex())
                .data("ice.event.captured", flight.getFlightIndex())
                .data("itineraryForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("itineraryForm:bookingType", "SA")
                .data("itineraryForm:tripType", "OneWay")
                .data("itineraryForm:board", flight.getOrigin())
                .data("itineraryForm:board_idx", "")
                .data("itineraryForm:dest", flight.getDestination())
                .data("itineraryForm:dest_idx", "")
                .data("itineraryForm:dd", DATE.format(flight.getDeparture()))
                .data("itineraryForm:j_id50", "ANY");

        addStandardInput(con);

        doc = con
                .data("itineraryForm:j_id78FirstPane", "height:100%;width:77%;")
                .data("itineraryForm:j_id78SecondPane", "height:100%;width:20%;")
                .data("itineraryForm:j_id78InPercent", "")
                .data("itineraryForm:searchResult:0:_idcl", "")
                .data("itineraryForm:_idcl", "")
                .data(flight.getFlightIndex(), "")
                .data("itineraryForm:_idcl", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", flight.getFlightIndex())
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        if (!doc.text().contains(String.valueOf(flight.getFlightNumber()))) {
            log(doc.html());
            throw new TravelUsException("Unable to add flight to Itinerary");
        }

        log(END);
    }

    public void unSelectFlight(Flight flight)
            throws IOException, TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;
        String s = flight.getFlightIndex();
        Integer i = Integer.valueOf(s.substring(s.length() - 1));
        s = s.substring(0, s.length() - 1) + String.valueOf(i + 1);

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .data("ice.event.target", s)
                .data("ice.event.captured", s)
                .data("itineraryForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("itineraryForm:bookingType", "SA")
                .data("itineraryForm:tripType", "OneWay")
                .data("itineraryForm:board", flight.getOrigin())
                .data("itineraryForm:board_idx", "")
                .data("itineraryForm:dest", flight.getDestination())
                .data("itineraryForm:dest_idx", "")
                .data("itineraryForm:dd", DATE.format(flight.getDeparture()))
                .data("itineraryForm:j_id50", "ANY");

        addStandardInput(con);

        doc = con
                .data("itineraryForm:j_id78FirstPane", "height:100%;width:77%;")
                .data("itineraryForm:j_id78SecondPane", "height:100%;width:20%;")
                .data("itineraryForm:j_id78InPercent", "")
                .data("itineraryForm:searchResult:0:_idcl", "")
                .data("itineraryForm:_idcl", "")
                .data(s, "")
                .data("itineraryForm:_idcl", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", s)
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        doc = extractInnerHTML(doc, 3);

        if (!doc.data().equals("")) {
            log(doc.html());
            throw new TravelUsException("Unable to remove flight from Itinerary");
        }

        log(END);
    }

    public List<Passenger> getAvailablePassengers(String origin, String destination, String departure)
            throws IOException, TravelUsException {

        log(STARTING);

        List<Passenger> passengers = new ArrayList<Passenger>();
        Document doc;
        Connection con;
        String fullName;
        String login;
        String id;
        String phone;
        String email;

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .cookie(NSC, nsc)
                .data("ice.event.target", "itineraryForm:bookItineraryPanel-accept")
                .data("ice.event.captured", "itineraryForm:bookItinerary")
                .data("itineraryForm:bookItinerary", "Continue")
                .data("itineraryForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("itineraryForm:bookingType", "SA")
                .data("itineraryForm:tripType", "OneWay")
                .data("itineraryForm:board", origin)
                .data("itineraryForm:board_idx", "")
                .data("itineraryForm:dest", destination)
                .data("itineraryForm:dest_idx", "")
                .data("itineraryForm:dd", departure)
                .data("itineraryForm:j_id50", "ANY");
        addStandardInput(con);

        doc = con
                .data("itineraryForm:j_id78FirstPane", "height:100%;width:77%;")
                .data("itineraryForm:j_id78SecondPane", "height:100%;width:20%;")
                .data("itineraryForm:j_id78InPercent", "")
                .data("itineraryForm:searchResult:0:_idcl", "")
                .data("itineraryForm:_idcl", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "itineraryForm:bookItinerary")
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        if (!doc.html().contains("redirect url=\"/us/search/passengers.jsf\"")) {
            log(doc.html());
            throw new TravelUsException("Unable to verify all flights were selected");
        }

        doc = Jsoup.connect(URI_SELECT_PASSENGERS)
                .referrer(URI_ITINERARY)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .timeout(mTimeout)
                .get();

        phone = doc.select("td[id=passengersForm:j_id56-0-1]").first().select("input").attr("value");
        email = doc.select("td[id=passengersForm:j_id61-0-1]").first().select("input").attr("value");

        log(phone);
        log(email);

        for (Element element : doc.select("table[class=iceSelMnyCb]").first().select(TR)) {
            fullName = element.text();
            id = element.select("input").first().attr("id");
            login = element.select("input").first().attr("value");
            passengers.add(new Passenger(fullName, id, login, phone, email));
        }

        log(String.format("Parsed %d passengers", passengers.size()));

        log(END);
        return passengers;
    }


    public Passenger selectPassenger(Passenger pax, StringList selectedPax)
            throws IOException, TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_SELECT_PASSENGERS)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .data("ice.event.target", pax.getId())
                .data("ice.event.captured", "passengersForm:eligibles")
                .data("passengersForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("passengersForm:phoneNum", pax.getPhone())
                .data("passengersForm:phoneNumType", "M")
                .data("passengersForm:emailAddress", pax.getEmail())
                .data("passengersForm:guestPassNum", "0")
                .data("passengersForm:currency", "USZ")
                .data("passengersForm:divMilesId", "");

        for (String s : selectedPax.getStrings())
            con.data("passengersForm:eligibles", s);

        addStandardInput(con);

        doc = con
                .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                .data("passengersForm:j_id32InPercent", "")
                .data("passengersForm:etDateOfBirth", "")
                .data("passengersForm:etGender", "FEMALE")
                .data("passengersForm:etRedressNumber", "")
                .data("passengersForm:etKnownTraveler", "")
                .data("passengersForm:gpFirstName", "")
                .data("passengersForm:gpMiddleName", "")
                .data("passengersForm:gpLastName", "")
                .data("passengersForm:j_id225", "NONE")
                .data("passengersForm:gpDateOfBirth", "")
                .data("passengersForm:gpGender", "FEMALE")
                .data("passengersForm:gpRedressNumber", "")
                .data("passengersForm:gpKnownTraveler", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "")
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        Document d1 = extractInnerHTML(doc, 0);
        if (!d1.text().contains(pax.getName()))
            d1 = extractInnerHTML(doc, 1);
        if (!d1.text().contains(pax.getName()))
            d1 = extractInnerHTML(doc, 2);
        if (!d1.text().contains(pax.getName()))
            d1 = extractInnerHTML(doc, 3);
        if (!d1.text().contains(pax.getName())) {
            log(doc.html());
            throw new TravelUsException("Unable to select passenger");
        }

        //TODO parse MX / UM

        log(END);

        return pax;
    }

    public float getPrice(Passenger pax, List<String> selectedPax)
            throws IOException, TravelUsException {

        log(STARTING);

        Document doc;
        Connection con;
        float price;

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_SELECT_PASSENGERS)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .data("ice.event.target", "passengersForm:j_id75")
                .data("ice.event.captured", "passengersForm:j_id75")
                .data("passengersForm:j_id75", "Price It!")
                .data("passengersForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("passengersForm:phoneNum", pax.getPhone())
                .data("passengersForm:phoneNumType", "M")
                .data("passengersForm:emailAddress", pax.getEmail())
                .data("passengersForm:guestPassNum", "0")
                .data("passengersForm:currency", "USZ")
                .data("passengersForm:divMilesId", "");

        for (String s : selectedPax)
            con.data("passengersForm:eligibles", s);

        addStandardInput(con);

        doc = con
                .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                .data("passengersForm:j_id32InPercent", "")
                .data("passengersForm:etDateOfBirth", "")
                .data("passengersForm:etGender", "FEMALE")
                .data("passengersForm:etRedressNumber", "")
                .data("passengersForm:etKnownTraveler", "")
                .data("passengersForm:gpFirstName", "")
                .data("passengersForm:gpMiddleName", "")
                .data("passengersForm:gpLastName", "")
                .data("passengersForm:j_id225", "NONE")
                .data("passengersForm:gpDateOfBirth", "")
                .data("passengersForm:gpGender", "FEMALE")
                .data("passengersForm:gpRedressNumber", "")
                .data("passengersForm:gpKnownTraveler", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "passengersForm:j_id245")
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        Document d = extractInnerHTML(doc, 0);
        price = Float.parseFloat(d.select("span[id=passengersForm:j_id265]").first().text().substring(1));

        if (!d.text().contains("Total:")) {
            Document d0 = extractInnerHTML(doc, 0);
            log(d0 == null ? "" : d0.html());
            Document d1 = extractInnerHTML(doc, 1);
            log(d1 == null ? "" : d1.html());

            log(doc.html());
            throw new TravelUsException("Unable to price PNR");
        }

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_SELECT_PASSENGERS)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .data("ice.event.target", "passengersForm:j_id245")
                .data("ice.event.captured", "passengersForm:j_id245")
                .data("passengersForm:j_id245", "X")
                .data("passengersForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("passengersForm:phoneNum", pax.getPhone())
                .data("passengersForm:phoneNumType", "M")
                .data("passengersForm:emailAddress", pax.getEmail())
                .data("passengersForm:guestPassNum", "0")
                .data("passengersForm:currency", "USZ")
                .data("passengersForm:divMilesId", "");

        for (String s : selectedPax)
            con.data("passengersForm:eligibles", s);

        addStandardInput(con);

        con
                .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                .data("passengersForm:j_id32InPercent", "")
                .data("passengersForm:etDateOfBirth", "")
                .data("passengersForm:etGender", "FEMALE")
                .data("passengersForm:etRedressNumber", "")
                .data("passengersForm:etKnownTraveler", "")
                .data("passengersForm:gpFirstName", "")
                .data("passengersForm:gpMiddleName", "")
                .data("passengersForm:gpLastName", "")
                .data("passengersForm:j_id225", "NONE")
                .data("passengersForm:gpDateOfBirth", "")
                .data("passengersForm:gpGender", "FEMALE")
                .data("passengersForm:gpRedressNumber", "")
                .data("passengersForm:gpKnownTraveler", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "passengersForm:j_id245")
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        log(END);

        return price;
    }

    public Reservation completeBooking(Passenger pax, StringList selectedPax)
            throws IOException, TravelUsException, ParseException {

        Reservation res = new Reservation("", new Date(), "");
        log(STARTING);

        Document doc;
        Connection con;

        con = Jsoup.connect(URI_SEND_RECEIVE_UPDATES)
                .referrer(URI_SELECT_PASSENGERS)
                .userAgent(USER_AGENT)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .cookie(ICE_SESSIONS, profileSession + "#1")
                .data("ice.event.target", "passengersForm:j_id165")
                .data("ice.event.captured", "passengersForm:j_id165")
                .data("passengersForm:j_id165", "Continue")
                .data("passengersForm", "")
                .data("javax.faces.RenderKitId", "")
                .data("passengersForm:phoneNum", pax.getPhone())
                .data("passengersForm:phoneNumType", "M")
                .data("passengersForm:emailAddress", pax.getEmail())
                .data("passengersForm:guestPassNum", "0")
                .data("passengersForm:currency", "USZ")
                .data("passengersForm:divMilesId", "");

        for (String s : selectedPax.getStrings())
            con.data("passengersForm:eligibles", s);

        addStandardInput(con);

        doc = con
                .data("passengersForm:j_id32FirstPane", "height:100%;width:59%;")
                .data("passengersForm:j_id32SecondPane", "height:100%;width:38%;")
                .data("passengersForm:j_id32InPercent", "")
                .data("passengersForm:etDateOfBirth", "")
                .data("passengersForm:etGender", "FEMALE")
                .data("passengersForm:etRedressNumber", "")
                .data("passengersForm:etKnownTraveler", "")
                .data("passengersForm:gpFirstName", "")
                .data("passengersForm:gpMiddleName", "")
                .data("passengersForm:gpLastName", "")
                .data("passengersForm:j_id225", "NONE")
                .data("passengersForm:gpDateOfBirth", "")
                .data("passengersForm:gpGender", "FEMALE")
                .data("passengersForm:gpRedressNumber", "")
                .data("passengersForm:gpKnownTraveler", "")
                .data("ice.session", profileSession)
                .data("ice.view", "1")
                .data("ice.focus", "passengersForm:j_id165")
                .data("rand", getRandom())
                .timeout(mTimeout)
                .post();

        if (!doc.html().contains("us/search/confirmation.jsf")) {
            log(doc.html());
            throw new TravelUsException("Unable to complete booking");
        }

        doc = Jsoup.connect(URI_BOOKING_CONFIRMATION)
                .referrer(URI_SELECT_PASSENGERS)
                .userAgent(USER_AGENT)
                .cookie(NSC, nsc)
                .cookie(JSESSIONID, sessionId).get();

        String pnr = doc.getElementById("confirmationForm:j_id32").text().substring(32);
        log(pnr);
        res.setPnr(pnr);
        for (Element element : doc.select("tr[id^=confirmationForm:j_id62:]")) {
            Flight flight = new Flight();
            String flightNumber = element.select("span[id$=j_id66]").text();
            String departure = element.select("span[id$=j_id70]").text();
            String origin = element.select("span[id$=j_id74]").text();
            String destination = element.select("span[id$=j_id78]").text();
            String departureTime = element.select("span[id$=j_id82]").text();
            String arrival = element.select("span[id$=j_id87]").text();
            String arrivalAlt = element.select("span[id$=j_id88]").text();

            Date arrivalTime;
            Integer flightNum = Integer.parseInt(flightNumber);
            Date departureDate = LONG_DATE.parse(departure);
            Date departureTimeParsed = SHORT_TIME.parse(departureTime);
            departureDate = new Date(departureDate.getTime() + departureTimeParsed.getTime() + getOffset());
            if (arrival.length() > 0) {
                arrivalTime = SHORT_TIME.parse(arrival);
                arrivalTime = new Date(departureDate.getTime() + arrivalTime.getTime() + getOffset());
            } else {
                arrivalTime = SHORT_TIME.parse(arrivalAlt);
                arrivalTime = new Date(departureDate.getTime() + arrivalTime.getTime() + getOffset());
            }
            flight.setFlightNumber(flightNum);
            flight.setOrigin(origin);
            flight.setDestination(destination);
            flight.setDeparture(departureDate);
            flight.setArrival(arrivalTime);

            res.getFlights().add(flight);
        }

        for (Element element : doc.select("td[id=confirmationForm:j_id101-2-0]").select("tbody").select(TR)) {
            String name = element.select("span[id$=:j_id111]").text();
            String relationship = element.select("span[id$=:j_id114]").text();
            String ticketNumber = element.select("span[id$=:j_id117]").text();

            res.getPassengers().add(new Passenger(name, relationship, ticketNumber));
        }

        log(END);
        return res;
    }

    public List<String> getNotices() throws IOException, TravelUsException {

        log(STARTING);

        Document doc;
        List<String> notices = new ArrayList<String>();

        doc = getProfilePage();
        for (Element row : doc.getElementById("profileForm:alerts").select("tbody").select("span[id$=:j_id126]"))
            notices.add(row.text());

        log(String.format("Returning %d notices", notices.size()));
        log(END);

        return notices;
    }

    //</editor-fold>
    //<editor-fold desc="Private Methods">

    private Document getProfilePage() throws IOException, TravelUsException {

        Document doc;
        doc = Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                .cookie(JSESSIONID, sessionId)
                .cookie(NSC, nsc)
                .userAgent(USER_AGENT)
                .timeout(mTimeout).get();

        if (doc.text().contains(LOGIN_PAGE_ERROR_MESSAGE)) {
            if (!doLogin(mUsername, mPassword))
                throw new TravelUsException("Unable to login");
            else {
                doc = Jsoup.connect(URI_PROFILE).referrer(URI_ITINERARY)
                        .cookie(JSESSIONID, sessionId)
                        .cookie(NSC, nsc)
                        .userAgent(USER_AGENT)
                        .timeout(mTimeout).get();
            }
        }
        return doc;
    }

    private void extractFlights(Date desiredDeparture, List<Flight> flights, Document doc) {

        Integer flightNumber;
        String departureOrigin;
        Date departureDate;
        Integer start;
        Integer end;
        String actualOrigin;
        String arrivalString;
        Date arrivalDate;
        String destinationReturned;
        String firstClassString;
        Integer coach;
        String flightIndex;

        for (Element row : doc.select(DATA_TABLE_ROW)) {
            try {
                flightNumber = Integer.parseInt(row.select(BOOK_FLIGHT_NUMBER_0).text().substring(3));
                departureOrigin = row.select(BOOK_DEPARTURE_0).text();
                departureDate = new Date(desiredDeparture.getTime()
                        + SHORT_TIME.parse(departureOrigin).getTime() + getOffset());
                start = departureOrigin.indexOf(PARENTHESIS_START);
                end = departureOrigin.lastIndexOf(PARENTHESIS_END);
                actualOrigin = departureOrigin.substring(start + 1, end);

                arrivalString = row.select(BOOK_ARRIVAL_0).text();
                if (arrivalString.equals(""))
                    arrivalString = row.select(BOOK_ARRIVAL_ALT_0).text();
                arrivalDate = new Date(desiredDeparture.getTime()
                        + SHORT_TIME.parse(arrivalString).getTime() + getOffset());
                if (arrivalDate.getTime() < departureDate.getTime())
                    arrivalDate = new Date(arrivalDate.getTime() + DAY);
                start = arrivalString.indexOf(PARENTHESIS_START);
                end = arrivalString.lastIndexOf(PARENTHESIS_END);
                destinationReturned = arrivalString.substring(start + 1, end);

                firstClassString = row.select(BOOK_FIRST_CLASS_0).text();
                coach = Integer.parseInt(row.select(BOOK_COACH_0).text());

                flightIndex = row.select("td[id$=0:j_id196-0-0]").select("input").first().attr("name");

                flights.add(new Flight(flightNumber, departureDate, actualOrigin,
                        destinationReturned, arrivalDate,
                        firstClassString.length() == 0 ? 0 : Integer.parseInt(firstClassString),
                        coach, flightIndex));

                if (row.select(BOOK_FLIGHT_NUMBER_1).text().length() > 0)
                    extractSecondLeg(desiredDeparture, flights, row);

                if (row.select(BOOK_FLIGHT_NUMBER_2).text().length() > 0)
                    extractThirdLeg(desiredDeparture, flights, row);
            } catch (Exception e) {
                log(e.getMessage());
            }
        }
    }

    private void extractSecondLeg(Date desiredDeparture, List<Flight> flights, Element row)
            throws ParseException {

        Integer flightNumber;
        String departureOrigin;
        Date departureDate;
        Integer start;
        Integer end;
        String actualOrigin;
        String arrivalString;
        Date arrivalDate;
        String destinationReturned;
        String firstClassString;
        Integer coach;
        String flightIndex;

        flightNumber = Integer.parseInt(row.select(BOOK_FLIGHT_NUMBER_1).text().substring(3));
        departureOrigin = row.select(BOOK_DEPARTURE_1).text();
        departureDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(departureOrigin).getTime() + getOffset());
        start = departureOrigin.indexOf(PARENTHESIS_START);
        end = departureOrigin.lastIndexOf(PARENTHESIS_END);
        actualOrigin = departureOrigin.substring(start + 1, end);

        arrivalString = row.select(BOOK_ARRIVAL_1).text();
        if (arrivalString.equals(""))
            arrivalString = row.select(BOOK_ARRIVAL_ALT_1).text();
        arrivalDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(arrivalString).getTime() + getOffset());
        if (arrivalDate.getTime() < departureDate.getTime())
            arrivalDate = new Date(arrivalDate.getTime() + DAY);
        start = arrivalString.indexOf(PARENTHESIS_START);
        end = arrivalString.lastIndexOf(PARENTHESIS_END);
        destinationReturned = arrivalString.substring(start + 1, end);

        firstClassString = row.select(BOOK_FIRST_CLASS_1).text();
        coach = Integer.parseInt(row.select(BOOK_COACH_1).text());

        flightIndex = row.select("td[id$=1:j_id194-0-0]").select("input").first().attr("name");

        flights.add(new Flight(flightNumber, departureDate, actualOrigin,
                destinationReturned, arrivalDate,
                firstClassString.length() == 0 ? 0 : Integer.parseInt(firstClassString),
                coach, flightIndex));
    }

    private void extractThirdLeg(Date desiredDeparture, List<Flight> flights, Element row)
            throws ParseException {

        Integer flightNumber;
        String departureOrigin;
        Date departureDate;
        Integer start;
        Integer end;
        String actualOrigin;
        String arrivalString;
        Date arrivalDate;
        String destinationReturned;
        String firstClassString;
        Integer coach;
        String flightIndex;

        flightNumber = Integer.parseInt(row.select(BOOK_FLIGHT_NUMBER_2).text().substring(3));
        departureOrigin = row.select(BOOK_DEPARTURE_2).text();
        departureDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(departureOrigin).getTime() + getOffset());
        start = departureOrigin.indexOf(PARENTHESIS_START);
        end = departureOrigin.lastIndexOf(PARENTHESIS_END);
        actualOrigin = departureOrigin.substring(start + 1, end);

        arrivalString = row.select(BOOK_ARRIVAL_2).text();
        if (arrivalString.equals(""))
            arrivalString = row.select(BOOK_ARRIVAL_ALT_2).text();
        arrivalDate = new Date(desiredDeparture.getTime()
                + SHORT_TIME.parse(arrivalString).getTime() + getOffset());
        if (arrivalDate.getTime() < departureDate.getTime())
            arrivalDate = new Date(arrivalDate.getTime() + DAY);
        start = arrivalString.indexOf(PARENTHESIS_START);
        end = arrivalString.lastIndexOf(PARENTHESIS_END);
        destinationReturned = arrivalString.substring(start + 1, end);

        firstClassString = row.select(BOOK_FIRST_CLASS_2).text();
        coach = Integer.parseInt(row.select(BOOK_COACH_2).text());

        flightIndex = row.select("td[id$=2:j_id194-0-0]").select("input").first().attr("name");

        flights.add(new Flight(flightNumber, departureDate, actualOrigin,
                destinationReturned, arrivalDate,
                firstClassString.length() == 0 ? 0 : Integer.parseInt(firstClassString),
                coach, flightIndex));
    }

    private void addStandardInput(Connection con) {
        con
                .data("ice.submit.partial", "false")
                .data("ice.event.type", "onclick")
                .data("ice.event.alt", "false")
                .data("ice.event.ctrl", "false")
                .data("ice.event.shift", "false")
                .data("ice.event.meta", "undefined")
                .data("ice.event.x", "163")
                .data("ice.event.y", "275")
                .data("ice.event.left", "false")
                .data("ice.event.right", "false")
                .data("icefacesCssUpdates", "")
                .data("javax.faces.ViewState", "1");
    }

    private List<Passenger> getPassengers(Element table) {

        log(STARTING);

        Element row;
        String name;
        String relationship;
        String ticket;

        List<Passenger> passengers = new ArrayList<Passenger>();
        for (Element element : table.select(DATA_TABLE_ROW)) {
            row = element;
            name = row.select(SPAN).get(0).text();
            relationship = row.select(SPAN).get(1).text();
            ticket = row.select(SPAN).get(2).text();

            passengers.add(new Passenger(name, relationship, ticket));
        }

        log(String.format("end... returning %d passengers", passengers.size()));

        return passengers;
    }

    private List<Flight> getFlights(Element table, String pnr)
            throws ParseException {

        log(STARTING);

        List<Flight> flights = new ArrayList<Flight>();
        Element row;
        int flightNumber;
        Date departure;
        String origin;
        String destination;
        Date departureTime;
        Date arrival;

        for (Element element : table.select(DATA_TABLE_ROW)) {
            row = element;
            flightNumber = Integer.parseInt(row.select(SPAN).get(0).text());
            departure = LONG_DATE.parse(row.select(SPAN).get(1).text());
            origin = row.select(SPAN).get(2).text();
            destination = row.select(SPAN).get(3).text();
            departureTime = SHORT_TIME.parse(row.select(SPAN).get(4).text());
            arrival = SHORT_TIME.parse(row.select(SPAN).get(5).text());
            arrival = new Date(departure.getTime() + arrival.getTime() + getOffset());
            departure = new Date(departure.getTime() + departureTime.getTime() + getOffset());

            flights.add(new Flight(flightNumber, departure, origin,
                    destination, arrival, false, pnr, false));
        }

        log(String.format("end... returning %d flights", flights.size()));

        return flights;
    }

    private Flight parseAvailability(Flight flight, Document doc) {

        log(STARTING);

        Element table;

        try {
            Document d = extractInnerHTML(doc, 0);
            if (d == null) {
                log("Unable to extract inner HTML");
                return flight;
            }

            if (d.text().length() > 10) {

                counter = 0;
                table = d.getElementById(CHECK_PBT_FORM_AVAILABILITY1);
                for (Element row : table.select(CHECK_PBT_DATA_TABLE_ROW)) {

                    if (row.select(SPAN).get(1).text().contains(CHECK_PBT_FIRST)) continue;

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_TOTAL_CAPACITY)) {
                        flight.getFirstClass().setCapacity(Integer.valueOf(row.select(SPAN).get(1).text().equals("") ? "0" : row.select(SPAN).get(1).text()));
                        flight.getCoach().setCapacity(Integer.valueOf(row.select(SPAN).get(2).text().equals("") ? "0" : row.select(SPAN).get(2).text()));
                        continue;
                    }

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_AUTHORIZED)) continue;

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_BOOKED)) continue;

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_SPACE_AVAILABLE)) {
                        flight.getFirstClass().setSeatsAvailable(Integer.valueOf(row.select(SPAN).get(1).text().equals("") ? "0" : row.select(SPAN).get(1).text()));
                        flight.getCoach().setSeatsAvailable(Integer.valueOf(row.select(SPAN).get(2).text().equals("") ? "0" : row.select(SPAN).get(2).text()));
                        continue;
                    }

                    if (row.select(SPAN).get(0).text().contains(CHECK_PBT_NON_REVS_LISTED)) {
                        flight.getFirstClass().setNonRevsCount(Integer.valueOf(row.select(SPAN).get(1).text().equals("") ? "0" : row.select(SPAN).get(1).text()));
                        flight.getCoach().setNonRevsCount(Integer.valueOf(row.select(SPAN).get(2).text().equals("") ? "0" : row.select(SPAN).get(2).text()));
                    }
                }
            } else if (counter > 3) {
                return flight;
            } else {
                counter = counter + 1;
                return checkAvailability(flight);
//                flight.getFirstClass().setCapacity(Integer.valueOf(doc.select(CONTENT).get(3).text().equals("") ? "0" : doc.select(CONTENT).get(3).text()));
//                flight.getCoach().setCapacity(Integer.valueOf(doc.select(CONTENT).get(4).text().equals("") ? "0" : doc.select(CONTENT).get(4).text()));
//                flight.getFirstClass().setSeatsAvailable(Integer.valueOf(doc.select(CONTENT).get(9).text().equals("") ? "0" : doc.select(CONTENT).get(9).text()));
//                flight.getCoach().setSeatsAvailable(Integer.valueOf(doc.select(CONTENT).get(10).text().equals("") ? "0" : doc.select(CONTENT).get(10).text()));
//                flight.getFirstClass().setNonRevsCount(Integer.valueOf(doc.select(CONTENT).get(11).text().equals("") ? "0" : doc.select(CONTENT).get(11).text()));
//                flight.getCoach().setNonRevsCount(Integer.valueOf(doc.select(CONTENT).get(12).text().equals("") ? "0" : doc.select(CONTENT).get(12).text()));
            }

        } catch (Exception e) {
            log("===============");
            log(e.getMessage());
            log("===============");
        }

        log(String.format("end... returning flight = %s", flight.toString()));

        return flight;
    }

//    private Flight parseNonRevList(Flight flight, Document doc) {
//
//        log(STARTING);
//
//        Element table;
//
//        doc = extractInnerHTML(doc, 0);
//        if (doc == null) {
//            log("Unable to extract inner HTML");
//            return flight;
//        }
//
//        table = doc.getElementById(CHECK_PBT_FORM_NON_REVS).getElementById(CHECK_PBT_FORM_NON_REVS);
//        int position = 1;
//
//        if (table == null || table.text() == null || getName() == null || !table.text().contains(getName())) {
//            log(String.format("table is null: %s", table == null));
//            log(String.format("table.text() is null: %s", (table != null ? table.text() : null) == null));
//            log(String.format("getName() is null: %s", getName() == null));
//            flight.setBoardingPriority(0);
//            return flight;
//        }
//
//        for (Element row : table.select(CHECK_PBT_DATA_TABLE_ROW)) {
//
//            if (row.select(SPAN).get(0).text().contains(CHECK_PBT_BOARDING_PRIORITY)) continue;
//
//            if (row.select(SPAN).get(3).text().startsWith(getName()) && row.select(SPAN).get(2).text().equals(DATE.format(getSeniority()))) {
//                flight.setBoardingPriority(position);
//                break;
//            } else {
//                position += Integer.valueOf(row.select(SPAN).get(1).text());
//            }
//        }
//
//        log(String.format("end... returning flight = %s", flight.toString()));
//
//        return flight;
//    }

    private Document extractInnerHTML(Document doc, int index) {

        int size = doc.select(CONTENT).size();
        if (size <= index) return null;

        return Jsoup.parse(StringEscapeUtils.unescapeXml(doc.select(CONTENT).get(index).text()));
    }

    private void ParseUserData(Document doc)
            throws ParseException {
        String longName = doc.getElementById(PROFILE_NAME).text().toUpperCase();
        longName = longName.substring(longName.lastIndexOf(' ') + 1);

        if (longName.length() < 5) longName += "\\";
        else longName = longName.substring(0, 5);

        setName(longName);
        setBadge(doc.getElementById(PROFILE_BADGE).text());
        setSeniority(DATE.parse(doc.getElementById(PROFILE_SENIORITY).text()));
    }

    //</editor-fold>
}
